﻿using Furion.CMS.Application;
using Furion.CMS.Application.Dto;
using Furion.CMS.Core;
using Furion.CMS.Domain;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Furion.CMS.Web.Core.Account
{
    /// <summary>
    /// 常规账号登录处理
    /// </summary>
    [Injection(Named = LoginTypeConsts.Account)]
    public class AccountLoginHandler : ILoginHandler, ITransient
    {
        private readonly IRepository<UserPassport> _repository;
        private readonly IDistributedCache<string> _captchaCache;
        private readonly IDistributedCache<LoginLock> _loginLockCache;

        /// <summary>
        /// 构造函数
        /// </summary>
        public AccountLoginHandler(IRepository<UserPassport> repository,
            IDistributedCache<string> captchaCache,
            IDistributedCache<LoginLock> loginLockCache)
        {
            _repository = repository;
            _captchaCache = captchaCache;
            _loginLockCache = loginLockCache;
        }

        public async Task<UserPassport> VerifyAsync(Dictionary<string, object> fields, UserLoginRecord loginRecord, SiteSetting settings)
        {
            // 根据后台配置来检测验证码
            if (settings.IsLoginCaptcha)
            {
                fields.TryGetValue("captcha", out object code);
                fields.TryGetValue("timestamp", out object timestamp);
                var key = $"captcha_{timestamp}";
                var cacheCode = await _captchaCache.GetAsync(key);
                if (!cacheCode?.Equals(code.ToString(), StringComparison.OrdinalIgnoreCase) ?? true)
                {
                    throw new AccountLoginException("验证码错误");
                }
                // 清除验证码
                _captchaCache.Remove(key);
            }

            if (fields.TryGetValue(LoginVerifyType.Account.AccountField, out object account) &&
                fields.TryGetValue(LoginVerifyType.Account.PasswordField, out object password))
            {
                loginRecord.Method = "账号密码登录";

                var passport = await _repository.AsQueryable(false)
                      .Where(t => t.Account.Equals(account))
                      .Include(i => i.User)
                      .ThenInclude(i => i.Roles)
                      .Include(i => i.User)
                      .ThenInclude(i => i.Organizations)
                      .ThenInclude(i => i.Roles)
                      .AsSplitQuery()
                      .FirstOrDefaultAsync();

                if (passport == null)
                    throw new AccountLoginException(LoginVerifyType.Account.ErrorMessage, "账号不存在");

                if (passport.Password != password.ToString())
                {
                    // 安全处理
                    if (settings.IsSafeLock)
                    {
                        var failRecord = _loginLockCache.GetOrAdd(passport.Account, () => new LoginLock { FailNumber = 1 },
                              new Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions
                              {
                                  AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(settings.SafeLockDuration)
                              });

                        // 判断账号是否多次尝试登录时密码错误的锁定
                        if (failRecord.LockTime != null)
                        {
                            if (DateTimeOffset.UtcNow < failRecord.LockTime)
                                throw new AccountLoginException($"账号已被锁定,请 {settings.SafeLockDuration} 秒后尝试.");
                            else if (failRecord.FailNumber == 5)
                            {// 重置次数
                                failRecord.FailNumber = 0;
                            }
                        }

                        failRecord.FailNumber += 1;
                        if (failRecord.FailNumber >= settings.SafeVerifyMaxFialNumber)
                        {
                            failRecord.LockTime = DateTimeOffset.UtcNow.AddSeconds(settings.SafeLockDuration);
                        }
                        // 更换锁定信息缓存
                        _loginLockCache.Set(passport.Account, failRecord);
                    }
                    throw new AccountLoginException(LoginVerifyType.Account.ErrorMessage);
                }

                // 修改日志
                loginRecord.PassportId = passport.Id;
                _loginLockCache.Remove(passport.Account); // 删除锁定信息缓存
                return passport;
            }
            else
            {
                throw new AccountLoginException("请输入账号或密码");
            }
        }
    }
}
